package seatiger.tomcat.catalina.startup;

import seatiger.tomcat.catalina.*;
import seatiger.tomcat.catalina.core.StandardHost;
import seatiger.tomcat.naming.resources.ResourceAttributes;
import seatiger.tomcat.util.StringManager;

import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import java.io.File;
import java.net.URL;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * Host容器的监听器，在 Host容器启动阶段，
 * 寻找对应的项目位置，为其创建Context对象，并添加到当前的子容器中
 */
public class HostConfig extends BaseConfig<Host> implements Runnable {

    protected static final StringManager sm =
            StringManager.getManager(Constants.PACKAGE_STARTUP);

    /**
     * 已经部署过的应用
     */
    private Set<String> deployed = new HashSet<>();

    private Host host;

    private int checkInterval = 15;

    /**
     * 后台线程完成标志
     */
    private boolean threadDone = false;

    /**
     * 后台线程
     */
    private Thread thread = null;

    private String threadName = "HostConfig";

    /**
     * 记录contextName 对应的web.xml加载的时间
     */
    private Map<String,Long> webXmlLastModified = new HashMap<>();

    private boolean deployXML = false;
    private boolean liveDeploy = false;


    public HostConfig() {
        super(event -> {
            Host host = event.getLifecycle();
            return host;
        });
    }

    @Override
    protected Consumer<Container> getConsumer() {
        return host -> {
            if (host instanceof StandardHost){
                deployXML = ((StandardHost) host).isDeployXML();
                liveDeploy = ((StandardHost) host).isDeployXML();
            }
        };
    }

    //部署Context
    @Override
    public void start(Host host) {
        this.host = host;
        Logger logger = host != null ? host.getLogger() : null;
        if (host.getAutoDeploy()){
            logger.log("开始部署Context》》》》》》》》》》》》》》");
            deployApps();
            if (liveDeploy){
                threadStart();
            }
        }
    }

    /**
     * 创建部署线程
     */
    private void threadStart() {
        if (thread != null){
            return;
        }
        threadDone = false;
        threadName = "HostConfig[" + host.getName() + "]";
        thread = new Thread(this,threadName);
        thread.setDaemon(true);
        thread.start();
    }

    private void deployApps() {
        if (deployXML){
            deployDescriptors();
        }

//        deployWARs();
        deployDirectories();
    }

    private void deploy(Supplier<List<File>> supplier, Consumer<File> consumer){
        List<File> files = supplier.get();
        files.stream()
                .filter(file -> deployed.add(file.getName()))
                .forEach(file -> consumer.accept(file));
    }
    /**
     * 部署appBase 下文件夹应用
     */
    private void deployDirectories() {
        deploy(() -> getFiles(str -> true).stream().filter(file -> file.isDirectory()).collect(Collectors.toList())
                , dir -> {
                    File webInfo = new File(dir,"/WEB-INF");
                    if (!webInfo.exists() || !webInfo.isDirectory() || !webInfo.canRead()){
                        return;
                    }
                    String contextPath = "/" + dir.getName();
                    if (host.findChild(contextPath) != null){
                        return;
                    }
                    log(sm.getString("hostConfig.deployDir", dir.getName()));
                    try {
                        URL url = new URL("file", null, dir.getCanonicalPath());
                        ((Deployer) host).install(contextPath, url);
                    } catch (Throwable t) {
                        log(sm.getString("hostConfig.deployDir.error", dir.getName()),t);
                    }
                });
    }



    /**
     * 部署appBase下配置文件的应用
     */
    private void deployDescriptors() {
        deploy(() -> getFiles(str -> str.toLowerCase().endsWith(".xml"))
                ,file -> {
                    String fileName = file.getName();
                    String contextPath ="/" + fileName.substring(0, fileName.length() - 4);
                    if (host.findChild(contextPath) != null){
                        return;
                    }
                    log(sm.getString("hostConfig.deployDescriptor", fileName));
                    try {
                        URL config = new URL("file", null, file.getCanonicalPath());
                        ((Deployer) host).install(config);
                    } catch (Throwable t) {
                        log(sm.getString("hostConfig.deployDescriptor.error", file.getName()), t);
                    }
                });
    }

    private List<File> getFiles(Predicate<String> predicate){
        Predicate<String> defalut = str -> !str.equalsIgnoreCase("META-INF");
        defalut = defalut.and(str -> !str.equalsIgnoreCase("WEB-INF"));
        Predicate<String> pre = defalut.and(predicate);
        File f =new File(host.getAppBase());
        File appBase = f.isAbsolute() ? f : new File(System.getProperty("catalina.base"),host.getAppBase());
        if (!appBase.exists() || !appBase.isDirectory()){
            return new ArrayList<>();
        }
        String[] files = appBase.list();
        return Arrays.stream(files)
                .filter(file -> pre.test(file))
                .map(file -> new File(appBase,file))
                .collect(Collectors.toList());
    }

    @Override
    public void run() {
        while (!threadDone){
            threadSleep();
            deployApps();
            checkWebXmlLastModified();
        }
    }

    /**
     * 判断每个应用中的web.xml是否改变
     */
    private void checkWebXmlLastModified() {
        if (!(host instanceof Deployer)){
            return;
        }
        Deployer deployer = (Deployer) host;
        String[] contextNames = deployer.findDeployedApps();
        Arrays.stream(contextNames)
                .map(contextName -> deployer.findDeployedApp(contextName))
                .filter(context -> context instanceof Lifecycle )
                .forEach(context -> {
                    DirContext resources = context.getResources();
                    try {
                        ResourceAttributes webXmlAttributes =
                                (ResourceAttributes) resources.getAttributes("/WEB-INF/web.xml");

                        long newLastModified = webXmlAttributes.getLastModified();
                        Long lastModified = webXmlLastModified.get(context.getName());
                        if (lastModified == null){
                            webXmlLastModified.put(context.getName(),newLastModified);
                        }else{
                            if (lastModified != newLastModified){
                                webXmlLastModified.remove(context.getName());
                                ((Lifecycle)context).stop();
                                ((Lifecycle)context).start();
                            }
                        }

                    } catch (LifecycleException e){
                    } catch (NamingException e) {
                    }
                });
    }

    private void threadSleep() {
        try {
            Thread.sleep(checkInterval * 1000L);
        } catch (InterruptedException e) {
        }
    }

    @Override
    public void stop(Host container) {
        //停止后台线程
        threadStop();
        undeployApps();
        return;
    }

    /**
     * 卸载应用
     */
    private void undeployApps() {
        if (!(host instanceof Deployer)){
            return;
        }
        Deployer deployer = (Deployer) host;
        Arrays.stream(deployer.findDeployedApps()).forEach(contextPath -> {
            try {
                deployer.remove(contextPath);
            } catch (Throwable t) {
                log(sm.getString("hostConfig.undeploy.error",contextPath), t);
            }
        });

    }

    private void threadStop() {
        if (thread == null){
            return;
        }
        threadDone = true;
        thread.interrupt();
        try {
            thread.join();
        } catch (InterruptedException e) {
        }
        thread = null;
    }
}
